package cn.tang.tframe.springredis.lock;

import cn.tang.tframe.common.utils.lang.StringUtils;
import cn.tang.tframe.springredis.ISpringRedisCache;
import cn.tang.tframe.springredis.QtProjectRedisClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import redis.clients.jedis.Jedis;

/**
 * Created by tanghc on 17/1/23.
 */
public class RedisLock {
	static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
	private final static String STATUS_RETURN = "OK";
	private RedisLock(){}
	private static class RedisLockHolder{
		private static RedisLock instance = new RedisLock();
	}
	public static RedisLock getInstance(){return RedisLockHolder.instance;}
	/**
	 * redis全局锁,一次获取不到等待1ms继续获取,最长等待maxWaitMs
	 * @param lock 锁标示
	 * @param expireSec 锁定时间,秒
	 * @param maxWaitMs 获取锁最长等待的时间,毫秒,超过这个时间则获取锁失败
	 * @return
	 */
	public boolean acquireLock(final String lock, final long expireSec, final long maxWaitMs) {
		logger.debug("acquireLock start:[lock={}][expireSec={}][maxWaitMs={}]",lock,expireSec,maxWaitMs);
		if(StringUtils.isBlank(lock)){
			return false;
		}
		final String newlock = getLockKey(lock);
		long expireAt = System.currentTimeMillis() + maxWaitMs;
		boolean success = false;

		// 循环判断锁是否一直存在
		while (!success) {
			if (maxWaitMs > 0 && System.currentTimeMillis() > expireAt) {
				break;
			}
			final ISpringRedisCache<Object> springRedisCache = QtProjectRedisClient.getSpringRedisCache();
			success = springRedisCache.execute(new RedisCallback<Boolean>() {
				@Override
				public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
					try {
						String acquired = "";
						Jedis jedis = (Jedis) connection.getNativeConnection();
						acquired = jedis.set(springRedisCache.wrapPrefix(newlock), String.valueOf(expireSec), "NX", "EX", expireSec);
						if (STATUS_RETURN.equalsIgnoreCase(acquired)) {
							return true;
						} else {
							Thread.sleep(10);
							return false;
						}
					} catch (Exception e) {
						logger.error("获取锁资源出现异常|lockKey:[{}]" , lock , e);
					}
					return false;
				}
			});
		}
		return success;
	}
	private String getLockKey(String lock){
		return "lock:"+lock;
	}

	/**
	 * 解锁
	 */
	public void releaseLock(final String lock) {
		logger.debug("releaseLock start:[lock={}]",lock);
		final String newlock = getLockKey(lock);
		QtProjectRedisClient.getSpringRedisCache().del(newlock);
	}

	/**
	 * 获取全局锁。一次获取不到则失败
	 */
	public boolean acquireLockForOnce(final String lock, final int expireSec) {
		logger.debug("acquireLockForOnce start:[lock={}][expireSec={}]",lock,expireSec);
		if(StringUtils.isBlank(lock)){
			return false;
		}
		final String newlock = getLockKey(lock);
		final ISpringRedisCache<Object> springRedisCache = QtProjectRedisClient.getSpringRedisCache();
		return springRedisCache.execute(new RedisCallback<Boolean>() {
			@Override
			public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
				try {
					String acquired = "";
					Jedis jedis = (Jedis) connection.getNativeConnection();
					acquired = jedis.set(springRedisCache.wrapPrefix(newlock), String.valueOf(expireSec), "NX", "EX", expireSec);
					if (STATUS_RETURN.equalsIgnoreCase(acquired)) {
						return true;
					} else {
						Thread.sleep(1);
						return false;
					}
				} catch (Exception e) {
					logger.error("获取锁资源出现异常|lockKey:[{}]", lock, e);
				}
				return false;
			}
		});
	}
}
